﻿using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Badger.Vhdl;
using Weazel.Badger.Vhdl.Types;
using Weazel.Badger.Vhdl.Expressions;
using Weazel.Badger.Vhdl.Statements;

namespace Weazel.Willows.InterfaceGeneration
{
	public abstract class HardwareQueue 
		: MemoryMappedItem
	{
		private Weazel.Willows.Model.Queue queue;
		public Weazel.Willows.Model.Queue Queue
		{
			get { return queue; }
		}

		private ComponentDeclaration queueComponentDeclaration;
		public ComponentDeclaration QueueComponentDeclaration
		{
			get { return queueComponentDeclaration; }	
		}

		private HardwareQueueRegistry queueRegistry;
		public HardwareQueueRegistry QueueRegistry
		{
			get { return queueRegistry; }			
		}

		private Weazel.Badger.Vhdl.Signal readingEndDataSignal;
		public Weazel.Badger.Vhdl.Signal ReadingEndDataSignal
		{
			get { return readingEndDataSignal; }			
		}

		private Weazel.Badger.Vhdl.Signal readingEndCanReadSignal;
		public Weazel.Badger.Vhdl.Signal ReadingEndCanReadSignal
		{
			get { return readingEndCanReadSignal; }
		}
		private Weazel.Badger.Vhdl.Signal readingEndDoReadSignal;
		public Weazel.Badger.Vhdl.Signal ReadingEndDoReadSignal
		{
			get { return readingEndDoReadSignal; }
		}

		private Weazel.Badger.Vhdl.Signal writingEndDataSignal;
		public Weazel.Badger.Vhdl.Signal WritingEndDataSignal
		{
			get { return writingEndDataSignal; }
		}

		private Weazel.Badger.Vhdl.Signal writingEndCanWriteSignal;
		public Weazel.Badger.Vhdl.Signal WritingEndCanWriteSignal
		{
			get { return writingEndCanWriteSignal; }
		}

		private Weazel.Badger.Vhdl.Signal writingEndDoWriteSignal;
		public Weazel.Badger.Vhdl.Signal WritingEndDoWriteSignal
		{
			get { return writingEndDoWriteSignal; }
		}

		private Weazel.Badger.Vhdl.Signal noLongerEmptySignal;
		public Weazel.Badger.Vhdl.Signal NoLongerEmptySignal
		{
			get { return noLongerEmptySignal; }
		}

		private Weazel.Badger.Vhdl.Signal noLongerFullSignal;
		public Weazel.Badger.Vhdl.Signal NoLongerFullSignal
		{
			get { return noLongerFullSignal; }
		}

		public SignalAssignmentStatement GetAssignZeroToDoWrite(StatementCollection collection)
		{
			ConstantExpression zero = new ConstantExpression(
				collection,
				"0",
				new StdLogic(),
				null
			);

			SignalAssignmentStatement assignZeroDoWrite =
				new SignalAssignmentStatement(
					writingEndDoWriteSignal,
					zero
				);

			return assignZeroDoWrite;
		}

		public SignalAssignmentStatement GetAssignOneToDoWrite(StatementCollection collection)
		{
			ConstantExpression one = new ConstantExpression(
				collection,
				"1",
				new StdLogic(),
				null
			);

			SignalAssignmentStatement assignOneDoWrite =
				new SignalAssignmentStatement(
					WritingEndDoWriteSignal,
					one
				);

			return assignOneDoWrite;
		}

		public SignalAssignmentStatement GetAssignZeroToDoRead(StatementCollection collection)
		{
			ConstantExpression zero = new ConstantExpression(
				collection,
				"'0'",
				new StdLogic(),
				null
			);

			SignalAssignmentStatement assignZeroToDoRead =
				new SignalAssignmentStatement(
					readingEndDoReadSignal,
					zero
				);

			return assignZeroToDoRead;
		}

		/// <summary>
		/// Get the widtd in bits of this connection
		/// </summary>
		public int BitWidth
		{
			get
			{
				return queue.BitWidth;
			}
		}

		/// <summary>
		/// Get the number of addresses (32 bits)
		/// needed by this connection
		/// </summary>
		public int AddressesNeeded
		{
			get
			{
				return BitWidth / 32 + 1;
			}
		}

		private void initializeSignals()
		{
			System.Diagnostics.Trace.Assert(queue != null);
			
			// initialize reading end signals
			this.readingEndDataSignal = queue.BitWidth > 1 ?
					new Weazel.Badger.Vhdl.Signal(new StdLogicVector(queue.BitWidth), null)
				: new Weazel.Badger.Vhdl.Signal(new StdLogic(), null);

			this.readingEndCanReadSignal = new Weazel.Badger.Vhdl.Signal(new StdLogic(), null);
			this.readingEndDoReadSignal = new Weazel.Badger.Vhdl.Signal(new StdLogic(), null);

			// initialize writing end signals
			this.writingEndDataSignal = queue.BitWidth > 1 ?
					new Weazel.Badger.Vhdl.Signal(new StdLogicVector(queue.BitWidth), null)
				: new Weazel.Badger.Vhdl.Signal(new StdLogic(), null);

			this.writingEndCanWriteSignal = new Weazel.Badger.Vhdl.Signal(new StdLogic(), null);
			this.writingEndDoWriteSignal = new Weazel.Badger.Vhdl.Signal(new StdLogic(), null);

			// get (and possibly create) a component declaration for the hw queue implementation
			this.queueComponentDeclaration = 
				queueRegistry.GetQueueComponentDeclaration(queue.BitWidth, queue.Length);

			this.noLongerEmptySignal = new Signal(new StdLogic(), null);
			this.noLongerFullSignal = new Signal(new StdLogic(), null);
		}

		public HardwareQueue(MemoryMap memoryMap, HardwareQueueRegistry queueRegistry, Weazel.Willows.Model.Queue queue)
			: base(memoryMap)
		{
			this.queue = queue;
			this.queueRegistry = queueRegistry;
			initializeSignals();
		}

		/// <summary>
		/// Insert the queue into a user logic entity and hook up wireing.
		/// </summary>
		/// <param name="entity"></param>
		public virtual void InsertQueue(UserLogicEntity entity)
		{
			// add a component declaration of the queue (if one isnt already present)
			if (!entity.DeclarativeItems.Contains(QueueComponentDeclaration))
				entity.DeclarativeItems.Add(QueueComponentDeclaration);

			// setup and add a portmap for the queue
			PortMap portMap =
				new PortMap(Queue.UniqueName, QueueComponentDeclaration.Name);

			// for each port, by declaration position ..
			for (int i = 0; i < QueueComponentDeclaration.Count; i++)
			{
				string portName =
					QueueComponentDeclaration.PortNameAtPosition(i);

				if (portName == FifoElementEntity.ClockPortName)
				{
					portMap.Add(FifoElementEntity.ClockPortName, entity.Bus2IpClockPort.Name);
				}
				else if (portName == FifoElementEntity.ResetPortName)
				{
					portMap.Add(FifoElementEntity.ResetPortName, entity.Bus2IpResetPort.Name);
				}
				else if (portName == FifoElementEntity.DataOutPortName)
				{
					portMap.Add(FifoElementEntity.DataOutPortName, this.ReadingEndDataSignal.Name);
				}
				else if (portName == FifoElementEntity.CanReadPortName)
				{
					portMap.Add(FifoElementEntity.CanReadPortName, this.ReadingEndCanReadSignal.Name);
				}
				else if (portName == FifoElementEntity.DoReadPortName)
				{
					portMap.Add(FifoElementEntity.DoReadPortName, this.ReadingEndDoReadSignal.Name);
				}
				else if (portName == FifoElementEntity.DataInPortName)
				{
					portMap.Add(FifoElementEntity.DataInPortName, this.WritingEndDataSignal.Name);
				}
				else if (portName == FifoElementEntity.CanWritePortName)
				{
					portMap.Add(FifoElementEntity.CanWritePortName, this.WritingEndCanWriteSignal.Name);
				}
				else if (portName == FifoElementEntity.DoWritePortName)
				{
					portMap.Add(FifoElementEntity.DoWritePortName, this.WritingEndDoWriteSignal.Name);
				}
				else if (portName == HardwareQueueEntity.NoLongerEmptyPortName)
				{
					portMap.Add(HardwareQueueEntity.NoLongerEmptyPortName, this.noLongerEmptySignal.Name);
				}
				else if (portName == HardwareQueueEntity.NoLongerFullPortName)
				{
					portMap.Add(HardwareQueueEntity.NoLongerFullPortName, this.noLongerFullSignal.Name);
				}
				else
					throw new NotSupportedException(portName);
			}

			entity.AddPortMap(portMap);
		}

		/// <summary>
		/// Method will insert required signals into the user logic entity.
		/// Inheriting methods should also call this method.
		/// </summary>
		/// <param name="entity">User logic entity to add required signals to</param>
		public virtual void InsertIntoUserLogicEntity(UserLogicEntity entity)
		{
			// add writing end signals
			entity.AddDeclarativeItem(this.writingEndCanWriteSignal);
			entity.AddSignal(this.writingEndCanWriteSignal);

			entity.AddDeclarativeItem(this.writingEndDataSignal);
			entity.AddSignal(this.writingEndDataSignal);

			entity.AddDeclarativeItem(this.writingEndDoWriteSignal);
			entity.AddSignal(this.writingEndDoWriteSignal);

			// add reading end signals
			entity.AddDeclarativeItem(this.readingEndCanReadSignal);
			entity.AddSignal(this.readingEndCanReadSignal);

			entity.AddDeclarativeItem(this.readingEndDoReadSignal);
			entity.AddSignal(this.readingEndDoReadSignal);

			entity.AddDeclarativeItem(this.readingEndDataSignal);
			entity.AddSignal(this.readingEndDataSignal);

			// no longer empty/full signals
			entity.AddDeclarativeItem(this.noLongerEmptySignal);
			entity.AddSignal(this.noLongerEmptySignal);

			entity.AddDeclarativeItem(this.noLongerFullSignal);
			entity.AddSignal(this.noLongerFullSignal);
		}
	}
}
